﻿using System;
using System.Collections.Generic;
using System.Text;
using centrafuse.Plugins;
using SciLorsGroovesharkAPI.Groove;
using System.Windows.Forms;
using System.Data;
using System.Threading;
using System.IO;
using System.Linq;
using SciLorsGroovesharkAPI.Groove.Functions;
using System.Net;
using System.Drawing;
using System.Timers;
using Un4seen.Bass;
using centrafuse;
using SciLorsGroovesharkAPI.Groove.Music;

namespace Grooveshark
{
    public partial class Grooveshark : CFPlugin
    {
        private const string PLUGIN_NAME = "Grooveshark";
        private int audioStream = 0;
        
        private int _zone = 0;

        bool _hasControl = false;

        private const string PLAYLIST_SCREEN = "GroovesharkPlaylist";
        private const string SEARCH_RESULT_SCREEN = "GroovesharkSearchResults";

        private int ClientUserID = -1;
        private string username = string.Empty;
        private string password = string.Empty;
        private bool login = false;

        private object ClientLock = new object();
        private GroovesharkClient _client;
        /// <summary>
        /// Make sure to lock ClientLock before calling Client
        /// </summary>
        private GroovesharkClient Client
        {
            get
            {
                if (_client == null)
                {
                    VerifyGrooveFix();
                    ShowInfo("Connecting to Grooveshark...");
                    _client = new GroovesharkClient(false);
                    _client.UseGZip = true;
                    _client.AutoReconnectWithoutMessageBox = true;
                    
                    if (login)
                    {
                        ShowInfo("Logging in...");
                        var loginResult = Client.Login(username, password);
                        if (loginResult.result.userID != 0)
                            ClientUserID = loginResult.result.userID;
                        else
                            throw new Exception("Incorrect username or password");
                    }
                    HideInfo();
                } 
                return _client;
            }
        }

        WebClient webClient;

        SYNCPROC songEndCallback;
        public Grooveshark()
        {
            CF_params.hasBasicSettings = true;
            songEndCallback = new SYNCPROC(SongEndCallback);
        }

        private void ShowInfo(string message)
        {
            this.Invoke(new MethodInvoker(delegate()
            {
                CF_systemCommand(CF_Actions.SHOWINFO, message);
            }));
        }
        private void HideInfo()
        {
            this.Invoke(new MethodInvoker(delegate()
                {
                    CF_systemCommand(CF_Actions.HIDEINFO);
                }));
        }

        private void LoadSettings()
        {
            this.CF_params.displayName = this.pluginLang.ReadField("/APPLANG/GROOVESHARK/DISPLAYNAME");

            string loginString = this.pluginConfig.ReadField("/APPCONFIG/LOGIN");
            if (!bool.TryParse(loginString, out login))
                login = false;

            if (login)
            {
                username = this.pluginConfig.ReadField("/APPCONFIG/USERNAME");

                string _encryptedPassword = this.pluginConfig.ReadField("/APPCONFIG/PASSWORD");

                if (!String.IsNullOrEmpty(_encryptedPassword))
                {
                    try
                    {
                        password = EncryptionHelper.DecryptString(_encryptedPassword, Setup.PASSWORD);
                    }
                    catch (Exception ex)
                    {
                        ReportException(ex);
                    }
                }
            }
        }

        private void VerifyGrooveFix()
        {
            System.Reflection.Assembly assembly = System.Reflection.Assembly.GetCallingAssembly();
            string dllFolderPath = Path.GetDirectoryName(assembly.Location);
            string grooveFixPath = Path.Combine(dllFolderPath, "GrooveFix.xml");

            string currentGrooveFixPath = Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), @"Plugins\Grooveshark\GrooveFix.xml");
            if (File.Exists(currentGrooveFixPath))
                File.Copy(currentGrooveFixPath, grooveFixPath, true);
                
        }

        private int? _initialBufferSize = null;
        public int InitialBufferSize
        {
            get
            {
                if (!_initialBufferSize.HasValue)
                {
                    string currentBufferSizeStr = this.pluginConfig.ReadField("/APPCONFIG/BUFFERSIZE");
                    int currentBufferSize = 0;
                    if (!int.TryParse(currentBufferSizeStr, out currentBufferSize))
                        currentBufferSize = 250;

                    _initialBufferSize = currentBufferSize * 1024;
                }
                return _initialBufferSize.Value;
            }
        }

        private void ShowPlaylistScreen()
        {
            this.CF3_initSection(PLAYLIST_SCREEN);
            SetupPlaylistScreen();
        }

        private void ShowSearchResultScreen()
        {
            this.CF3_initSection(SEARCH_RESULT_SCREEN);
            SetupSearchResultScreen();
        }

        SearchArtist.SearchArtistResult currentSong;
        Image currentSongImage;

        private const string AlbumArtFormat = "http://images.grooveshark.com/static/albums/200_{0}";
        private void PlaySong(SearchArtist.SearchArtistResult song)
        {
            currentSong = song;

            CF_updateText("songLabel", song.SongName);
            CF_updateText("artistLabel", song.ArtistName);
            CF_updateText("albumLabel", song.AlbumName);

            CF_controlAudioStream(audioStream, CF_AudioAction.Stop);
            CF_controlAudioStream(audioStream, CF_AudioAction.Free);

            DataRow songRow = playlistTable.Rows.Cast<DataRow>().Single(r => (r["Object"] as SearchArtist.SearchArtistResult).SongID.Equals(song.SongID));

            this.playlistBindingSource.Position = songRow.Table.Rows.IndexOf(songRow);
            playlistView.SelectedIndex = songRow.Table.Rows.IndexOf(songRow);

            PlayGroovesharkSong(song);

            CF_setPictureImage("albumArtBox", null);

            webClient.CancelAsync();
            if (!string.IsNullOrEmpty(song.CoverArtFileName))
            {
                string albumUrl = string.Format(AlbumArtFormat, song.CoverArtFileName);
                Uri albumUri = new Uri(albumUrl, UriKind.RelativeOrAbsolute);
                webClient.DownloadDataAsync(albumUri, currentSong.SongID);
            }
        }

        public bool ShuffleState
        {
            get
            {
                string field = this.pluginConfig.ReadField("/APPCONFIG/SHUFFLE");
                bool val;
                if (bool.TryParse(field, out val))
                    return val;
                else
                    return false;
            }
            set
            {
                this.pluginConfig.WriteField("/APPCONFIG/SHUFFLE", value.ToString(), true);
            }
        }

        private void PlayGroovesharkSong(SearchArtist.SearchArtistResult song)
        {
            ThreadPool.QueueUserWorkItem(delegate(object obj)
            {
                Thread.Sleep(2000);
                if (currentSong == null || currentSong.SongID != song.SongID || currentSong.ArtistID != song.ArtistID)
                    return;

                lock (ClientLock)
                {
                    try
                    {
                        var result = Client.GetMusicStream(song.SongID, song.ArtistID);
                        audioStream = PlayStream(result);
                    }
                    catch
                    {
                        ResetClient();
                        try
                        {
                            var result = Client.GetMusicStream(song.SongID, song.ArtistID);
                            audioStream = PlayStream(result);
                        }
                        catch (Exception ex)
                        {
                            ReportException(ex);
                        }
                    }
                }
                
            });
        }

        private void ReportException(Exception ex)
        {
            this.BeginInvoke(new MethodInvoker(delegate()
            {
                CFDialogParams dialogParams = new CFDialogParams("Error occurred: " + ex.Message);
                CF_displayDialog(CF_Dialogs.OkBox, dialogParams);
            }));
        }

        private void ResetClient()
        {
            if (_client != null)
            {
                try
                {
                    _client.Logout();
                }
                catch { }
            }
            _client = null;
        }

        
        public int PlayStream(GroovesharkAudioStream groovesharkStream)
        {
            try
            {
                StreamWrapper currentWrapper = StreamWrapper.GetCurrent();
                if (currentWrapper != null)
                    
                {
                    Bass.BASS_ChannelStop(currentWrapper.StreamID);
                    Bass.BASS_StreamFree(currentWrapper.StreamID);
                }
                StreamWrapper wrapper = StreamWrapper.CreateNew_DestroyOld(groovesharkStream);
                int channel = Bass.BASS_StreamCreateFileUser(BASSStreamSystem.STREAMFILE_BUFFER, BASSFlag.BASS_STREAM_AUTOFREE, wrapper.FILEPROC, IntPtr.Zero);
                if (channel != 0)
                {
                    wrapper.StreamID = channel;
                    Bass.BASS_ChannelPlay(channel, false);
                    Bass.BASS_ChannelSetSync(channel, BASSSync.BASS_SYNC_END, 0L, songEndCallback, IntPtr.Zero);
                }
                return channel;
            }
            catch (Exception ex)
            {
                CFTools.writeError(ex.Message, ex.StackTrace);
                ReportException(ex);
                return 0;
            }
        }

        private void SongEndCallback(int handle, int channel, int data, IntPtr user)
        {
            this.BeginInvoke(new MethodInvoker(delegate()
                {
                    NextSong(false);
                }));
        }

        private void PlayPause()
        {
            CF_AudioStatus audioStatus = CF_getAudioStatus(audioStream);

            switch (audioStatus)
            {
                case CF_AudioStatus.Paused:
                    CF_controlAudioStream(audioStream, CF_AudioAction.Play);
                    CF_setPlayPauseButton(false, _zone);
                    break;
                case CF_AudioStatus.Playing:
                    CF_controlAudioStream(audioStream, CF_AudioAction.Pause);
                    CF_setPlayPauseButton(true, _zone);
                    break;
                case CF_AudioStatus.Stopped:
                    NextSong(true);
                    break;
                case CF_AudioStatus.Stalled:
                    break;
            }
        }

        private void NextSong(bool loopAround)
        {
            if(playlistTable.Rows.Count == 0)
                return;

            SearchArtist.SearchArtistResult songToPlay = null;
            if (currentSong == null)
            {
                if (ShuffleState)
                {
                    songToPlay = shuffledPlaylist.First.Value;
                }
                else
                {
                    songToPlay = this.playlistTable.Rows[0]["Object"] as SearchArtist.SearchArtistResult;
                }
            }
            else
            {
                if (ShuffleState)
                {
                    var thisSong = shuffledPlaylist.Find(currentSong);
                    if (thisSong.Next == null && !loopAround)
                    {
                        Stop();
                        return;
                    }
                    else
                    {
                        songToPlay = thisSong.Next != null ? thisSong.Next.Value : thisSong.List.First.Value;
                    }
                }
                else
                {
                    int currentSongIx = GetCurrentSongRowIx();
                    int songIx = currentSongIx + 1;

                    if (songIx >= this.playlistTable.Rows.Count)
                    {
                        if (loopAround)
                        {
                            songIx = 0;
                        }
                        else
                        {
                            Stop();
                            return;
                        }
                    }
                    songToPlay = this.playlistTable.Rows[songIx]["Object"] as SearchArtist.SearchArtistResult;
                }
            }

            PlaySong(songToPlay);
        }

        private void Stop()
        {
            currentSong = null;

            CF_updateText("songLabel", string.Empty);
            CF_updateText("artistLabel", string.Empty);
            CF_updateText("albumLabel", string.Empty);

            CF_controlAudioStream(audioStream, CF_AudioAction.Stop);
            CF_controlAudioStream(audioStream, CF_AudioAction.Free);

            CF_setPictureImage("albumArtBox", null);
        }

        private int GetCurrentSongRowIx()
        {
            int currentSongIx = -1;
            for (int i = 0; i < this.playlistTable.Rows.Count; i++)
            {
                DataRow row = this.playlistTable.Rows[i];
                if (row["Object"].Equals(currentSong))
                {
                    currentSongIx = i;
                    break;
                }
            }
            return currentSongIx;
        }

        private void PreviousSong()
        {
            if (playlistTable.Rows.Count == 0)
                return;

            SearchArtist.SearchArtistResult songToPlay = null;
            if (currentSong == null)
            {
                if (ShuffleState)
                {
                    songToPlay = shuffledPlaylist.First.Value;
                }
                else
                {
                    songToPlay = this.playlistTable.Rows[0]["Object"] as SearchArtist.SearchArtistResult;
                }
            }
            else
            {
                if (ShuffleState)
                {
                    var thisSong = shuffledPlaylist.Find(currentSong);
                    songToPlay = thisSong.Previous != null ? thisSong.Previous.Value : thisSong.List.Last.Value;
                }
                else
                {
                    int currentSongIx = GetCurrentSongRowIx();
                    int songIx = currentSongIx - 1;
                    if (songIx < 0)
                        songIx = this.playlistTable.Rows.Count - 1;
                    songToPlay = this.playlistTable.Rows[songIx]["Object"] as SearchArtist.SearchArtistResult;
                }
            }

            PlaySong(songToPlay);
        }

        #region overrides

        public override void CF_pluginShow()
        {
            base.CF_pluginShow();
            _hasControl = true;
        }

        public override void CF_pluginClose()
        {
            webClient.Dispose();
        }

        public override void CF_localskinsetup()
        {
            this.CF3_initSection(PLAYLIST_SCREEN);
            SetupPlaylistScreen();
        }

        public override void CF_pluginInit()
        {

            this.CF3_initPlugin("Grooveshark", true);

            this.CF_localskinsetup();

            LoadSettings();

            this.CF_params.pauseAudio = true;

            playlistTable = new DataTable();
            playlistTable.Columns.Add("Object", typeof(SearchArtist.SearchArtistResult));
            playlistTable.Columns.Add("SongName", typeof(string));

            shuffledPlaylist = new LinkedList<SearchArtist.SearchArtistResult>();

            playlistBindingSource = new BindingSource();
            playlistBindingSource.DataSource = playlistTable;

            searchResultsTable = new DataTable();
            searchResultsTable.Columns.Add("Name", typeof(string));
            searchResultsTable.Columns.Add("Artist", typeof(string));
            searchResultsTable.Columns.Add("Album", typeof(string));
            searchResultsTable.Columns.Add("MetaName", typeof(string));
            searchResultsTable.Columns.Add("Object", typeof(object));

            searchResultsBindingSource = new BindingSource();
            searchResultsBindingSource.DataSource = searchResultsTable;

            webClient = new WebClient();
            webClient.DownloadDataCompleted += new DownloadDataCompletedEventHandler(webClient_DownloadDataCompleted);
        }

        public override string CF_pluginCMLData(CF_CMLTextItems textItem)
        {
            switch (textItem)
            {
                case CF_CMLTextItems.MainTitle:
                    return currentSong == null ? String.Empty : currentSong.SongName;
                case CF_CMLTextItems.MediaArtist:
                    return currentSong == null ? String.Empty : currentSong.ArtistName;
                case CF_CMLTextItems.MediaTitle:
                    return currentSong == null ? String.Empty : currentSong.SongName;
                case CF_CMLTextItems.MediaAlbum:
                    return currentSong == null ? String.Empty : currentSong.AlbumName;
                case CF_CMLTextItems.MediaSource: case CF_CMLTextItems.MediaStation:
                    return "Grooveshark";
                case CF_CMLTextItems.MediaDuration:
                    return GetDuration();
                case CF_CMLTextItems.MediaPosition:
                    return GetPosition();
                case CF_CMLTextItems.MediaMode:
                    return "WTF IS MODE";
                

                default:
                    return base.CF_pluginCMLData(textItem);
            }
        }

        private string GetPosition()
        {
            if (audioStream == 0) return string.Empty;

            // length in bytes
            long len = Bass.BASS_ChannelGetPosition(audioStream, BASSMode.BASS_POS_BYTES);
            if (len <= 0)
            {
                return string.Empty;
            }
            // the time length
            int seconds = (int)Bass.BASS_ChannelBytes2Seconds(audioStream, len);

            int minutes = seconds / 60;
            seconds = seconds % 60;
            return string.Format(timeFormat, minutes, seconds);
        }

        private const string timeFormat = "{0}:{1:00}";
        private string GetDuration()
        {
            if (audioStream == 0) return string.Empty;  

            // length in bytes
            long len = Bass.BASS_ChannelGetLength(audioStream, BASSMode.BASS_POS_BYTES);
            if (len <= 0)
            {
                return string.Empty;
            }
            // the time length
            int seconds = (int)Bass.BASS_ChannelBytes2Seconds(audioStream, len);

            int minutes = seconds / 60;
            seconds = seconds % 60;
            return string.Format(timeFormat, minutes, seconds);
        }

        public override bool CF_pluginCMLCommand(string command, string[] strparams, CF_ButtonState state, int zone)
        {
            if (!_hasControl)
                return false;

            _zone = zone;

            if (state != CF_ButtonState.Click && state != CF_ButtonState.HoldClick)
                return false;

            switch (command)
            {
                case "Grooveshark.Favorites": 
                    this.MyFavorites_LongPressed();
                    return true;
                case "Centrafuse.Main.PlayPause":
                    PlayPause();
                    return true;
                case "Centrafuse.Main.Rewind":
                    PreviousSong();
                    return true;
                case "Centrafuse.Main.FastForward":
                    NextSong(true);
                    return true;

                default:
                    return base.CF_pluginCMLCommand(command, strparams, state, zone);
            }
        }

        public override DialogResult CF_pluginShowSetup()
        {
            // Return DialogResult.OK for the main application to update from plugin changes.
            DialogResult returnvalue = DialogResult.Cancel;

            try
            {
                // Creates a new plugin setup instance. If you create a CFDialog or CFSetup you must
                // set its MainForm property to the main plugins MainForm property.
                Setup setup = new Setup(this.MainForm, this.pluginConfig, this.pluginLang);
                returnvalue = setup.ShowDialog();

                if (returnvalue == DialogResult.OK)
                {
                    _initialBufferSize = null;
                    //Load the new settings
                    LoadSettings();
                }

                lock (ClientLock)
                {
                    if (_client != null)
                    {
                        _client.Logout();
                    }
                    _client = null;
                }

                setup.Close();
                setup = null;
            }
            catch (Exception errmsg) { CFTools.writeError(errmsg.ToString()); }

            return returnvalue;
        }

        public override void CF_pluginPause()
        {
            _hasControl = false;

            if (CF_getAudioStatus(audioStream) == CF_AudioStatus.Playing)
            {
                CF_controlAudioStream(audioStream, CF_AudioAction.Pause);
                CF_setPlayPauseButton(true, _zone);
            }
        }

        public override void CF_pluginResume()
        {
            _hasControl = true;

            if (CF_getAudioStatus(audioStream) == CF_AudioStatus.Paused)
            {
                CF_controlAudioStream(audioStream, CF_AudioAction.Play);
                CF_setPlayPauseButton(false, _zone);
            }
        }

        #endregion

        
    }
}
